#include "defines.h"
#include "kozos.h"
#include "lib.h"
#include "consdrv.h"
#include "kinfo.h"
#include "netdef.h"
#include "netlib.h"
#include "ni.h"
#include "ping.h"
#include "arp.h"
#include "dns.h"
#include "command.h"
#include "ntp.h"
#include "udp.h"

#define RECV_BUFFER_SIZE 64

static const char *COMMANDS[] = {"arp", "dns",
                                 "echo", "exit", "help", "niconf", "ntp", "ping", "ps",
                                 "reset", "resource", "sleep", "uptime",
                                 NULL};

#define DNS_TIMEOUT 3
static uint8 dns_server[] = DNS_SERVER;

static int getIP(const char *q, uint8 IP[])
{
    return q && (parse_IP(IP, q) == 0 ||
                 dns_resolve(q, IP, DNS_TIMEOUT, dns_server) >= 0);
}

static void command_ntp(const char arg[])
{
#define NTP_TIMEOUT 3
    uint8 server[4];
    const udp_receiver *ur;
    char *p;

    if (!getIP(arg, server)) {
        puts("No server.\n");
        return;
    }

    ur = udp_use(UDP_PORT_NTP);
    if (!ur) {
        puts("port is used.\n");
        return;
    }

    ntp_query(server);

    if (kz_recva(ur->mbox, NULL, &p, SECS(NTP_TIMEOUT)) == THREAD_ID_ERR) {
        puts("No answer.\n");

    } else {
        ip_header *ihdr = (ip_header *)(p + sizeof(ethernet_header));
        udp_header *uhdr = (udp_header *)(ihdr + 1);
        ntp_packet *np = (ntp_packet *)(uhdr + 1);

        if (!memcmp(ihdr->ip_src_IP, server, 4) &&
            uhdr->udp_src_port == UDP_PORT_NTP &&
            uhdr->udp_length - sizeof(*uhdr) == sizeof(*np) &&
            (np->li_vn_mode & 0x7) == 4)
        {
            ntp_time nt = ntp_gettime(np);

            putval(nt.hour, 2);
            puts(":");
            putval(nt.min, 2);
            puts(":");
            putval(nt.sec, 2);
            puts("\n");

        } else {
            puts("Bad answer.\n");
        }

        kz_kmfree(p);
    }

    udp_close(ur);
}

void command_main(int argc, char *argv)
{
    int c;
    kz_msgbox_id_t msgbox_in;
    char *recv_buf;
    int recv_len;

    recv_buf = kz_kmalloc(RECV_BUFFER_SIZE);
    msgbox_in = kz_getmsgbox();
    cons_use(msgbox_in);

    while (1) {
        char *p, *q;

        puts("command> ");

        // コマンド行を得る
        recv_len = 0;
        while (1) {
            kz_recv(msgbox_in, &c, NULL);

            if (c == '\r') {
                putc('\n');
                recv_buf[recv_len++] = '\0';
                break;

            } else if (c == '\b') {
                if (recv_len) {
                    puts("\b \b");
                    recv_len--;
                }

            } else if (c == '\t') {
                /* タブは無視 */

            } else if (recv_len < RECV_BUFFER_SIZE - 1) {
                putc(c);
                recv_buf[recv_len++] = c;
            }
        }

        // コマンド処理
        p = recv_buf;
        q = getarg(&p);
        if (!q) continue;

        if (!strcmp(q, "echo")) {
            q = getarg(&p);
            if (q) {
                puts(q);
                while (1) {
                    q = getarg(&p);
                    if (!q) break;
                    putc(' ');
                    puts(q);
                }
            }
            putc('\n');

        } else if (!strcmp(q, "sleep")) {
            int t;
#define SLEEP_MAX   100
            if ((q = getarg(&p)) && is_num(q) && (t = atoi(q)) >= 0 && t <= SLEEP_MAX)
            {

                kz_wait(SECS(t));

            } else {
                puts("invalid time. (0-");
                putval(SLEEP_MAX, 0);
                puts(")\n");
            }

        } else if (!strcmp(q, "help")) {
            const char **p;
            for(p = COMMANDS; *p; p++) {
                puts(*p);
                putc('\n');
            }

        } else if (!strcmp(q, "ps")) {
            kz_threadinfo_t *info, *p;

            puts("ID NAME PRIORITY STACKSIZE FLAGS\n");

            info = (kz_threadinfo_t *)kz_getthreads();

            for (p = info; p < info + THREAD_NUM; p++) {
                if (p->info.id == THREAD_ID_NULL)
                    break;

                putval(p->info.id, 0);
                putc(' ');
                if (p->is_current) putc('*');
                puts(p->info.name);
                putc(' ');
                putval(p->info.priority, 0);
                puts(" 0x");
                putxval(p->info.stacksize, 0);
                puts(" 0x");
                putxval(p->info.flags, 4);
                putc('\n');
            }

            kz_kmfree(info);

        } else if (!strcmp(q, "exit")) {
            break;

        } else if (!strcmp(q, "reset")) {
            kz_reset();

        } else if (!strcmp(q, "resource")) {
            char *p = kz_resource();
            kz_resourceinfo_t *q = (kz_resourceinfo_t *)p;
            int size;

            size = q->size - 1;
            q++;
            while (size--) {
                if (q->type == RESOURCE_THREAD) {
                    puts("THREAD: ");
                    putval(q->used, 0);
                    puts(" / ");
                    putval(q->num, 0);
                    puts("\n");
                } else if (q->type == RESOURCE_MEMORY) {
                    puts("MEM");
                    putval(q->size, 0);
                    puts(": ");
                    putval(q->used, 0);
                    puts(" / ");
                    putval(q->num, 0);
                    puts("\n");
                } else if (q->type == RESOURCE_MSGBOX) {
                    puts("MSGBOX: ");
                    putval(q->used, 0);
                    puts(" / ");
                    putval(q->num, 0);
                    puts("\n");
                }
                q++;
            }

            kz_kmfree(p);

        } else if (!strcmp(q, "uptime")) {
            uint32 H;
            uint16 M, S;

            H = div3216(kz_systime(), TICK_PER_SEC, NULL);
            H = div3216(H, 60, &S);
            H = div3216(H, 60, &M);

            putval(H, 2);
            putc(':');
            putval(M, 2);
            putc(':');
            putval(S, 2);
            putc('\n');

        } else if (!strcmp(q, "niconf")) {
            q = getarg(&p);

            if (q) {
                uint8 IP[4], gateway[4];
                int mask;

                if (parse_IP(IP, q) == -1)
                    goto fail;

                if (!(q = getarg(&p)) || !is_num(q))
                    goto fail;
                mask = atoi(q);

                if (!(q = getarg(&p)) || parse_IP(gateway, q) == -1)
                    goto fail;

                if (niconf_set(IP, mask, gateway) == -1)
                    goto fail;

                goto end;

fail:
                puts("Failed.\n");

end:
                ;

            } else {
                niconf_print();
            }

        } else if (!strcmp(q, "ping")) {
#define PING_DEFAULTSIZE 64
#define PING_MAXSIZE 1500
#define PING_WAIT 2
            uint8 dst_IP[4];
            int size = -1;

            q = getarg(&p);
            if (!getIP(q, dst_IP)) {
                puts("No target.\n");
                continue;
            }

            q = getarg(&p);
            if (!q) {
                size = PING_DEFAULTSIZE;
            } else if (is_num(q)) {
                size = atoi(q);
            }

            if (size >= 0 && size <= PING_MAXSIZE) {
                puts("PING ");
                print_IP(dst_IP);
                puts(" (");
                putval(size, 0);
                puts(" bytes)\n");
                if (ping_send(dst_IP, size) < 0) {
                    puts("ping_send failed.\n");
                    continue;
                }
                kz_wait(SECS(PING_WAIT));
            } else {
                puts("Bad size. (0-");
                putval(PING_MAXSIZE, 0);
                puts(")\n");
            }

        } else if (!strcmp(q, "arp")) {

            arp_print();

        } else if (!strcmp(q, "dns")) {
            char *q0;
            uint8 IP[4];
            int r = -1;

            q = getarg(&p);
            q0 = getarg(&p);

            if (!q) {
                puts("failed.\n");
                continue;
            }

            if (!strcmp(q, "-s") && q0) {
                if (parse_IP(IP, q0) == 0) {
                    memcpy(dns_server, IP, 4);
                } else {
                    puts("failed.\n");
                }
                continue;
            }

            puts("query "); puts(q); puts(" to "); print_IP(dns_server); puts("\n");
            r = dns_resolve(q, IP, DNS_TIMEOUT, dns_server);
            puts("-> ");

            if (r >= 0) {
                print_IP(IP); puts("\n");
            } else {
                puts("failed.\n");
            }

        } else if (!strcmp(q, "ntp")) {
            q = getarg(&p);
            command_ntp(q);

        } else {
            puts("unknown command.\n");
        }
    }

    cons_close(msgbox_in);
    kz_freemsgbox(msgbox_in);
    kz_kmfree(recv_buf);
}
